using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

namespace StudyCompanion.Commons.Encrypt;

/// <summary>
///     RSA算法类型
/// </summary>
public enum RSAType
{
    /// <summary>
    ///     SHA1
    /// </summary>
    RSA = 0,

    /// <summary>
    ///     RSA2 密钥长度至少为2048
    ///     SHA256
    /// </summary>
    RSA2
}

public class RSAHelper
{
    public static RSAHelper Rsa;
    private readonly Encoding _encoding;
    private readonly HashAlgorithmName _hashAlgorithmName;
    private readonly RSA _privateKeyRsaProvider;
    private readonly RSA _publicKeyRsaProvider;

    /// <summary>
    ///     实例化RSAHelper
    /// </summary>
    /// <param name="rsaType">加密算法类型 RSA SHA1;RSA2 SHA256 密钥长度至少为2048</param>
    /// <param name="encoding">编码类型</param>
    /// <param name="privateKey">私钥</param>
    /// <param name="publicKey">公钥</param>
    private RSAHelper(RSAType rsaType, Encoding encoding, string privateKey, string publicKey = null)
    {
        _encoding = encoding;
        if (!string.IsNullOrEmpty(privateKey)) _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey);

        if (!string.IsNullOrEmpty(publicKey)) _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey);

        _hashAlgorithmName = rsaType == RSAType.RSA ? HashAlgorithmName.SHA1 : HashAlgorithmName.SHA256;
    }

    public static void Initialize(string privateKey, string publicKey)
    {
        Rsa = new RSAHelper(RSAType.RSA2, Encoding.UTF8, privateKey, publicKey);
    }

    #region 使用私钥签名

    /// <summary>
    ///     使用私钥签名
    /// </summary>
    /// <param name="data">原始数据</param>
    /// <returns></returns>
    public string Sign(string data)
    {
        var dataBytes = _encoding.GetBytes(data);
        var signatureBytes = _privateKeyRsaProvider.SignData(dataBytes, _hashAlgorithmName, RSASignaturePadding.Pkcs1);
        return Convert.ToBase64String(signatureBytes);
    }

    #endregion

    #region 使用公钥验证签名

    /// <summary>
    ///     使用公钥验证签名
    /// </summary>
    /// <param name="data">原始数据</param>
    /// <param name="sign">签名</param>
    /// <returns></returns>
    public bool Verify(string data, string sign)
    {
        var dataBytes = _encoding.GetBytes(data);
        var signBytes = Convert.FromBase64String(sign);
        var verify =
            _publicKeyRsaProvider.VerifyData(dataBytes, signBytes, _hashAlgorithmName, RSASignaturePadding.Pkcs1);
        return verify;
    }

    #endregion

    #region 使用私钥创建RSA实例

    public RSA CreateRsaProviderFromPrivateKey(string privateKey)
    {
        var privateKeyBits = Convert.FromBase64String(privateKey);
        var rsa = RSA.Create();
        var rsaParameters = new RSAParameters();
        using (var binr = new BinaryReader(new MemoryStream(privateKeyBits)))
        {
            byte bt;
            ushort twobytes;
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130)
                binr.ReadByte();
            else if (twobytes == 0x8230)
                binr.ReadInt16();
            else
                throw new Exception("Unexpected value read binr.ReadUInt16()");
            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102)
                throw new Exception("Unexpected version");
            bt = binr.ReadByte();
            if (bt != 0x00)
                throw new Exception("Unexpected value read binr.ReadByte()");
            rsaParameters.Modulus = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.Exponent = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.D = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.P = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.Q = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.DP = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.DQ = binr.ReadBytes(GetIntegerSize(binr));
            rsaParameters.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
        }

        rsa.ImportParameters(rsaParameters);
        return rsa;
    }

    #endregion

    #region 使用公钥创建RSA实例

    public RSA CreateRsaProviderFromPublicKey(string publicKeyString)
    {
        // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
        byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
        byte[] seq;
        var x509Key = Convert.FromBase64String(publicKeyString);
        // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
        using (var mem = new MemoryStream(x509Key))
        {
            using (var binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
            {
                byte bt;
                ushort twobytes;
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte(); //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16(); //advance 2 bytes
                else
                    return null;
                seq = binr.ReadBytes(15); //read the Sequence OID
                if (!CompareBytearrays(seq, seqOid)) //make sure Sequence for OID is correct
                    return null;
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                    binr.ReadByte(); //advance 1 byte
                else if (twobytes == 0x8203)
                    binr.ReadInt16(); //advance 2 bytes
                else
                    return null;
                bt = binr.ReadByte();
                if (bt != 0x00) //expect null byte next
                    return null;
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte(); //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16(); //advance 2 bytes
                else
                    return null;
                twobytes = binr.ReadUInt16();
                byte lowbyte;
                byte highbyte = 0x00;
                if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                {
                    lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
                }
                else if (twobytes == 0x8202)
                {
                    highbyte = binr.ReadByte(); //advance 2 bytes
                    lowbyte = binr.ReadByte();
                }
                else
                {
                    return null;
                }

                byte[] modint =
                    { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
                var modsize = BitConverter.ToInt32(modint, 0);
                var firstbyte = binr.PeekChar();
                if (firstbyte == 0x00)
                {
                    //if first byte (highest order) of modulus is zero, don't include it
                    binr.ReadByte(); //skip this null byte
                    modsize -= 1; //reduce modulus buffer size by 1
                }

                var modulus = binr.ReadBytes(modsize); //read the modulus bytes
                if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
                    return null;
                int expbytes =
                    binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
                var exponent = binr.ReadBytes(expbytes);
                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                var rsa = RSA.Create();
                var rsaKeyInfo = new RSAParameters
                {
                    Modulus = modulus,
                    Exponent = exponent
                };
                rsa.ImportParameters(rsaKeyInfo);
                return rsa;
            }
        }
    }

    #endregion


    /// <summary>
    ///     XML公钥转成Pem公钥
    /// </summary>
    /// <param name="xmlPublicKey"></param>
    /// <returns></returns>
    public static string XmlPublicKeyToPem(string xmlPublicKey)
    {
        RSAParameters rsaParam;
        using (var rsa = new RSACryptoServiceProvider())
        {
            rsa.FromXmlString(xmlPublicKey);
            rsaParam = rsa.ExportParameters(false);
        }

        var param = new RsaKeyParameters(false, new BigInteger(1, rsaParam.Modulus),
            new BigInteger(1, rsaParam.Exponent));

        string pemPublicKeyStr;
        using (var ms = new MemoryStream())
        {
            using (var sw = new StreamWriter(ms))
            {
                var pemWriter = new PemWriter(sw);
                pemWriter.WriteObject(param);
                sw.Flush();

                var buffer = new byte[ms.Length];
                ms.Position = 0;
                ms.Read(buffer, 0, (int)ms.Length);
                pemPublicKeyStr = Encoding.UTF8.GetString(buffer);
            }
        }

        return pemPublicKeyStr;
    }

    /// <summary>
    ///     Pem公钥转成XML公钥
    /// </summary>
    /// <param name="pemPublicKeyStr"></param>
    /// <returns></returns>
    public static string PemPublicKeyToXml(string pemPublicKeyStr)
    {
        RsaKeyParameters pemPublicKey;
        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPublicKeyStr)))
        {
            using (var sr = new StreamReader(ms))
            {
                var pemReader = new PemReader(sr);
                pemPublicKey = (RsaKeyParameters)pemReader.ReadObject();
            }
        }

        var p = new RSAParameters
        {
            Modulus = pemPublicKey.Modulus.ToByteArrayUnsigned(),
            Exponent = pemPublicKey.Exponent.ToByteArrayUnsigned()
        };

        string xmlPublicKeyStr;
        using (var rsa = new RSACryptoServiceProvider())
        {
            rsa.ImportParameters(p);
            xmlPublicKeyStr = rsa.ToXmlString(false);
        }

        return xmlPublicKeyStr;
    }

    /// <summary>
    ///     XML私钥转成PEM私钥
    /// </summary>
    /// <param name="xmlPrivateKey"></param>
    /// <returns></returns>
    public static string XmlPrivateKeyToPem(string xmlPrivateKey)
    {
        RSAParameters rsaParam;
        using (var rsa = new RSACryptoServiceProvider())
        {
            rsa.FromXmlString(xmlPrivateKey);
            rsaParam = rsa.ExportParameters(true);
        }

        var param = new RsaPrivateCrtKeyParameters(
            new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent), new BigInteger(1, rsaParam.D),
            new BigInteger(1, rsaParam.P), new BigInteger(1, rsaParam.Q), new BigInteger(1, rsaParam.DP),
            new BigInteger(1, rsaParam.DQ),
            new BigInteger(1, rsaParam.InverseQ));

        string pemPrivateKeyStr;
        using (var ms = new MemoryStream())
        {
            using (var sw = new StreamWriter(ms))
            {
                var pemWriter = new PemWriter(sw);
                pemWriter.WriteObject(param);
                sw.Flush();

                var buffer = new byte[ms.Length];
                ms.Position = 0;
                ms.Read(buffer, 0, (int)ms.Length);
                pemPrivateKeyStr = Encoding.UTF8.GetString(buffer);
            }
        }

        return pemPrivateKeyStr;
    }

    /// <summary>
    ///     Pem私钥转成XML私钥
    /// </summary>
    /// <param name="pemPrivateKeyStr"></param>
    /// <returns></returns>
    public static string PemPrivateKeyToXml(string pemPrivateKeyStr)
    {
        RsaPrivateCrtKeyParameters pemPrivateKey;
        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPrivateKeyStr)))
        {
            using (var sr = new StreamReader(ms))
            {
                var pemReader = new PemReader(sr);
                var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
                pemPrivateKey = (RsaPrivateCrtKeyParameters)keyPair.Private;
            }
        }

        var p = new RSAParameters
        {
            Modulus = pemPrivateKey.Modulus.ToByteArrayUnsigned(),
            Exponent = pemPrivateKey.PublicExponent.ToByteArrayUnsigned(),
            D = pemPrivateKey.Exponent.ToByteArrayUnsigned(),
            P = pemPrivateKey.P.ToByteArrayUnsigned(),
            Q = pemPrivateKey.Q.ToByteArrayUnsigned(),
            DP = pemPrivateKey.DP.ToByteArrayUnsigned(),
            DQ = pemPrivateKey.DQ.ToByteArrayUnsigned(),
            InverseQ = pemPrivateKey.QInv.ToByteArrayUnsigned()
        };

        string xmlPrivateKeyStr;
        using (var rsa = new RSACryptoServiceProvider())
        {
            rsa.ImportParameters(p);
            xmlPrivateKeyStr = rsa.ToXmlString(true);
        }

        return xmlPrivateKeyStr;
    }

    /// <summary>
    ///     PEM格式私钥转换成XML格式私钥
    /// </summary>
    /// <param name="privateKey">PEM格式私钥，（纯文字字符，注意不带-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----）</param>
    /// <returns></returns>
    public static string RSAPToXml(string privateKey)
    {
        var privateKeyParam =
            (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
        return string.Format(
            "<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
            Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));
    }

    #region 解密

    public string Decrypt(string cipherText)
    {
        if (_privateKeyRsaProvider == null) throw new Exception("_privateKeyRsaProvider is null");

        return Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(Convert.FromBase64String(cipherText),
            RSAEncryptionPadding.Pkcs1));
    }

    public string Decrypt(List<string> cipherText)
    {
        if (_privateKeyRsaProvider == null) throw new Exception("_privateKeyRsaProvider is null");

        var dataDecrypt = "";
        cipherText.ForEach(x =>
            dataDecrypt +=
                Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(Convert.FromBase64String(x),
                    RSAEncryptionPadding.Pkcs1)));
        return dataDecrypt;
    }

    #endregion

    #region 加密

    public string Encrypt(string text)
    {
        if (_publicKeyRsaProvider == null) throw new Exception("_publicKeyRsaProvider is null");

        return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text),
            RSAEncryptionPadding.Pkcs1));
    }

    public string Encrypt(List<char[]> data)
    {
        if (_publicKeyRsaProvider == null) throw new Exception("_publicKeyRsaProvider is null");

        var encryptData = new List<string>();
        data.ForEach(x =>
            encryptData.Add(Convert.ToBase64String(
                _publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(string.Join("", x)),
                    RSAEncryptionPadding.Pkcs1))));
        return string.Join(',', encryptData);
    }

    #endregion

    #region 导入密钥算法

    private int GetIntegerSize(BinaryReader binr)
    {
        byte bt;
        int count;
        bt = binr.ReadByte();
        if (bt != 0x02)
            return 0;
        bt = binr.ReadByte();
        if (bt == 0x81)
        {
            count = binr.ReadByte();
        }
        else if (bt == 0x82)
        {
            var highbyte = binr.ReadByte();
            var lowbyte = binr.ReadByte();
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
            count = BitConverter.ToInt32(modint, 0);
        }
        else
        {
            count = bt;
        }

        while (binr.ReadByte() == 0x00) count -= 1;

        binr.BaseStream.Seek(-1, SeekOrigin.Current);
        return count;
    }

    private bool CompareBytearrays(byte[] a, byte[] b)
    {
        if (a.Length != b.Length)
            return false;
        var i = 0;
        foreach (var c in a)
        {
            if (c != b[i])
                return false;
            i++;
        }

        return true;
    }

    #endregion
}